
/**
 * 异步更新队列
 */
// 存储所有的watcher
const queue = []
// 标识现在是否正在刷新watcher队列
let flushing = false
// 标识，保证callbacks 数组中只有一个刷新watcher 队列的函数
let waiting = false
// 存放刷新watcher 队列的函数，或者用户调用 Vue.nextTick 传递的回调函数
const callbacks = []
// 标识浏览器当前任务队列中是否存在callbacks数组的函数
let pending = false
/**
 * 将watcher放入队列
 * @param {*} watcher 需要等待执行的watcher, 包括渲染watcher(执行vm._update那个)、用户watcher($nextTick())、computed
 */
export function queueWatcher(watcher) {
  // 防止重复入队
  if (!queue.includes(watcher)) {
    if (!flushing) {
      // 当前没有正在刷新 watcher 队列
      queue.push(watcher)
    } else {
      // 正在刷新watcher 队列， 比如用户watcher的回调函数中更改了某个响应式数据
      // 标记watcher是否已经入队
      let flag = false
      for(let i = queue.length - 1; i >= 0; i--) {
        if (queue[i].uid < watcher.uid) {
          // 找到刚好比前面的uid小的那个watcher,将当前watcher 插入到该位置后面, 跳出循环
          flag = true
          break
        }
      }
      // 如果没有比当前uid更小的watcher,则放到第一个
      if (!flag) {
        queue.unshift(watcher)
      }
      if (!waiting) {
        // 标识当前 callbacks 数组中还没有刷新 watcher 队列的函数
        // 保证 callbacks 数组中只有一个刷星 watcher 队列的函数
        // 因为如果有多个也没意义，第二个执行的时候，已经清空了watcher队列
        waiting = true
        nextTick(flushSchedulerQueue)
      }
    }
  }
} 

/**
 * 负责刷新 watcher 队列的函数，由flushCallbacks 函数调用
 */
function flushSchedulerQueue() {
  // 标识正在刷新 watcher 队列
  flushing = true
  // 给 watcher 队列排序，根据uid
  queue.sort((a,b) => a.uid - b.uid)
  // 遍历执行 watcher 的 run 方法
  while(queue.length) {
    const watcher = queue.shift()
    // 执行 run 方法
    watcher.run()
  }
  // watcher 队列刷新（执行）完毕
  flushing = waiting = false
}


/**
 * 将刷新 watcher 队列的函数或者用户调用的 Vue.nextTick 传递的回调函数放到 callbacks 数组
 * 如果当前浏览器任务duiliezho那个没有刷星 callbacks 的函数， 则将 flushCallbacks 函数放入任务队列
 * @param {*} cb 
 */
function nextTick(cb) {
  callbacks.push(cb)
  if (!pending) {
    // 当前队列没有刷星 callbacks 数组的函数
    Promise.resolve().then(flushCallbacks)
    // 更新标识，表示浏览器微任务队列中已经存在刷新 callbacks 数组的函数了
    pending = true
  }
}

// 负责执行 callbacks 数组的函数，执行 callbacks 数组中的所有函数
function flushCallbacks() {
  // 更新标识，表示浏览器任务队列中的 flushCallbacks 函数已经被拿到执行栈执行了
  // 新的 flushCallbacks 函数可以进入到微任务队列了
  pending = false
  while(callbacks.length) {
    // 取出第一个并执行
    callbacks.shift()()
  }
}